﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using BobcatUtility;

namespace BobcatMultiTouch
{
    /// <summary>
    /// This class listens for multitouch inputs on behalf of one form or control. It raises
    /// events when touch-events occur (down, up, move). The events include the touch point
    /// in both screen coordinates and client coordinates. The events are marshaled into the
    /// form's thread.
    /// </summary><remarks>
    /// Only touches that originally had a touch-down in the control will be raised to the control.
    /// The MTIM raises events in screen coordinates, and this class receives all of them. When it 
    /// receives a touch-down event it checks if the touch occured in the client area of the control
    /// we are managing. If it is outside the area of our client control we ignore it. If it is in
    /// our client area, we then "own" that touch, and we will raise all future events for it to
    /// out control (even if subsequent events are outside the client area).
    /// </remarks>
    public class MultiTouchInputClient
    {
        #region Events

        /// <summary>
        /// Event raised when a touch down event occurs.
        /// </summary>
        public event EventHandler<TouchArgs> TouchDownEvent;

        /// <summary>
        /// Event raised when a touch up event occurs.
        /// </summary>
        public event EventHandler<TouchArgs> TouchUpEvent;

        /// <summary>
        /// Event raised when a mopve event occurs.
        /// </summary>
        public event EventHandler<TouchArgs> TouchMoveEvent;

        #endregion

        #region Public methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public MultiTouchInputClient()
        {
        }

        /// <summary>
        /// Initializes this object and sets the control (or form) that it will
        /// manage touch events for. Events will be passed data in client coordinates
        /// for this control.
        /// </summary>
        public void initialize(Control control)
        {
            m_Control = control;
            m_iControlHWnd = control.Handle.ToInt32();

            // We register for events from the MTIM...
            MultiTouchInputManager mtim = MultiTouchInputManager.getInstance();
            mtim.TouchDownEvent += onTouchDown;
            mtim.TouchUpEvent += onTouchUp;
            mtim.TouchMoveEvent += onTouchMove;
        }

        /// <summary>
        /// Shuts down this object and unregisters for events.
        /// </summary>
        public void close()
        {
            // We unregister from events...
            MultiTouchInputManager mtim = MultiTouchInputManager.getInstance();
            mtim.TouchDownEvent -= onTouchDown;
            mtim.TouchUpEvent -= onTouchUp;
            mtim.TouchMoveEvent -= onTouchMove;
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Called when we recieve a touch-down event. If it is in the client area of our
        /// control we start to "own" this touch, and we raise the event to our observers.
        /// </summary>
        private void onTouchDown(object sender, TouchArgs e)
        {
            // We only raise touch-down events if we're managing an active control...
            if (canReceiveInput() == false)
            {
                return;
            }

            // We find the screen bounds of the control we're managing and see if the 
            // touch is inside them...
            Rectangle screenBounds = Utility.getScreenBounds(m_iControlHWnd);
            if (screenBounds.Contains(e.ScreenPosition) == false)
            {
                // The touch is not in our client area...
                return;
            }

            // The touch is in our client area, so we start to own it...
            m_setOwnedTouches[e.PointIndex] = true;

            // We convert the screen coords into client coords, and fire the event...
            e.ClientPosition = toClientPoint(e.ScreenPosition, screenBounds);
            Utility.CrossThreadInvoke(TouchDownEvent, this, e);
        }

        /// <summary>
        /// Called when a touch-up event is received. If we own it, we raise the event to
        /// our observers.
        /// </summary>
        private void onTouchUp(object sender, TouchArgs e)
        {
            // Do we own the point?
            if(m_setOwnedTouches.ContainsKey(e.PointIndex) == false)
            {
                // We don't own the point...
                return;
            }

            // We own the point, so we convert the point to client coords and raise
            // the event...
            Rectangle screenBounds = Utility.getScreenBounds(m_iControlHWnd);
            e.ClientPosition = toClientPoint(e.ScreenPosition, screenBounds);
            Utility.CrossThreadInvoke(TouchUpEvent, this, e);

            // We clear our ownership of this point...
            m_setOwnedTouches.Remove(e.PointIndex);
        }

        /// <summary>
        /// Called when we receive a touch-move event. If we own the point we raise the event
        /// to our observers.
        /// </summary>
        private void onTouchMove(object sender, TouchArgs e)
        {
            // Do we own the point?
            if (m_setOwnedTouches.ContainsKey(e.PointIndex) == false)
            {
                // We don't own the point...
                return;
            }

            // We own the point, so we convert the point to client coords and raise
            // the event...
            Rectangle screenBounds = Utility.getScreenBounds(m_iControlHWnd);
            e.ClientPosition = toClientPoint(e.ScreenPosition, screenBounds);
            Utility.CrossThreadInvoke(TouchMoveEvent, this, e);
        }

        /// <summary>
        /// Returns whether the control we are managing can receive input. This means that it
        /// is part of the active window.
        /// </summary>
        private bool canReceiveInput()
        {
            // We see if the top-level form that this control is a member of is active...
            return (Form.ActiveForm == m_Control.TopLevelControl);
        }

        /// <summary>
        /// We return the point relative to the client area for the point passed in.
        /// </summary>
        private Point toClientPoint(Point screenPoint, Rectangle screenBounds)
        {
            Point results = new Point();
            results.X = screenPoint.X - screenBounds.X;
            results.Y = screenPoint.Y - screenBounds.Y;
            return results;
        }
        
        #endregion

        #region Private data

        // The handle of the control (or form) we're managing. We need to keep this reference so that we can
        // translate coordinates into client coordinates for this control.
        private Control m_Control = null;
        private int m_iControlHWnd = 0;

        // The "set" of all touch-index that we own (i.e. that had a touch-down in our client area).
        private IDictionary<int, bool> m_setOwnedTouches = new Dictionary<int, bool>();

        #endregion

    }
}
